package com.katesoft.scale4j.common.utils;

import java.util.StringTokenizer;

import com.katesoft.scale4j.common.annotation.NullSafe;

/**
 * @author Santhosh Kumar T
 * @author kate2007
 */
public final class StringUtility {
   /**
    * if <code>obj</code> is null, returns empty string. otherwise returns
    * <code>obj.toString()</code>
    * 
    * @param obj
    *           any ref you want
    * @return <code>obj</code> or <code>obj.toString()</code>
    */
   public static String toString(Object obj) {
      return obj == null ? "" : obj.toString();
   }

   /**
    * returns true if <code>str</code> is null or its length is zero
    * 
    * @param str
    *           any char sequence you want
    * @return true if <code>str</code> is null or its length is zero.
    */
   public static boolean isEmpty(CharSequence str) {
      return str == null || str.length() == 0;
   }

   /**
    * returns true if <code>str</code> is null or it contains only white spaces.
    * <p/>
    * {@link Character#isWhitespace(char)} is used to test for whitespace
    * 
    * @param str
    *           any char sequence you want
    * @return true if <code>str</code> is null or it contains only white spaces.
    */
   @NullSafe
   public static boolean isWhitespace(CharSequence str) {
      if (str != null) {
         for (int i = 0; i < str.length(); i++) {
            if (!Character.isWhitespace(str.charAt(i))) {
               return false;
            }
         }
      }
      return true;
   }

   /**
    * Splits given string into tokens with delimiters specified. It uses StringTokenizer for
    * tokenizing.
    * 
    * @param str
    *           string to be tokenized
    * @param delim
    *           delimiters used for tokenizing
    * @param trim
    *           trim the tokens
    * @return non-null token array
    */
   public static String[] getTokens(String str, String delim, boolean trim) {
      StringTokenizer stok = new StringTokenizer(str, delim);
      String tokens[] = new String[stok.countTokens()];
      for (int i = 0; i < tokens.length; i++) {
         tokens[i] = stok.nextToken();
         if (trim) {
            tokens[i] = tokens[i].trim();
         }
      }
      return tokens;
   }

   /**
    * Converts first character in <code>str</code> to uppercase.
    * <p/>
    * This method can be called on string of any length.
    * 
    * @param str
    *           string to be converted
    * @return string with first letter changed to uppercase
    */
   public static String capitalize(String str) {
      if (str == null) {
         return null;
      }
      switch (str.length()) {
         case 0:
            return str;
         case 1:
            return str.toUpperCase();
         default:
            return Character.toUpperCase(str.charAt(0)) + str.substring(1);
      }
   }

   /**
    * Makes an underscored form from the expression in the string.
    * <p/>
    * Examples:
    * 
    * <pre class="prettyprint">
    * <p/>
    * underscore("activeRecord")   "active_record"
    * <p/>
    * underscore("ActiveRecord")    "active_record"
    * <p/>
    * underscore("firstName")       "first_name"
    * <p/>
    * underscore("FirstName")        "first_name"
    * <p/>
    * underscore("name")              "name"
    * </pre>
    * 
    * @param camelCaseWord
    *           the camel-cased word that is to be converted;
    * @return a lower-cased version of the input, with separate words delimited by the underscore
    *         character.
    */
   public static String underscore(String camel) {
      String camelCaseWord = camel;
      if (camelCaseWord == null) {
         return null;
      }
      camelCaseWord = camelCaseWord.trim();
      if (camelCaseWord.length() == 0) {
         return "";
      }
      camelCaseWord = camelCaseWord.replaceAll("([A-Z]+)([A-Z][a-z])", "$1_$2");
      camelCaseWord = camelCaseWord.replaceAll("([a-z\\d])([A-Z])", "$1_$2");
      return camelCaseWord.toLowerCase();
   }

   /**
    * Turns a non-negative number into an ordinal string used to denote the position in an ordered
    * sequence, such as 1st, 2nd, 3rd, 4th
    * 
    * @param number
    *           the non-negative number
    * @return the string with the number and ordinal suffix
    */
   public static String ordinalize(int number) {
      int modulo = number % 100;
      if (modulo >= 11 && modulo <= 13) {
         return number + "th";
      }
      switch (number % 10) {
         case 1:
            return number + "st";
         case 2:
            return number + "nd";
         case 3:
            return number + "rd";
         default:
            return number + "th";
      }
   }

   public static int[] toCodePoints(String str) {
      int count = str.codePointCount(0, str.length());
      int[] codePoints = new int[count];
      for (int cpIndex = 0, charIndex = 0; cpIndex < count; cpIndex++) {
         int cp = str.codePointAt(charIndex);
         codePoints[cpIndex] = cp;
         charIndex += Character.charCount(cp);
      }
      return codePoints;
   }

   /*-------------------------------------------------[ Literal ]---------------------------------------------------*/
   public static String toLiteral(char ch, boolean useRaw) {
      if (ch == '\'') {
         return "\\'";
      } else if (ch == '"') {
         return "\"";
      } else {
         return StringUtility.toLiteral(String.valueOf(ch), useRaw);
      }
   }

   public static String toLiteral(CharSequence str, boolean useRaw) {
      StringBuffer buf = new StringBuffer(str.length() + 25);
      for (int i = 0, len = str.length(); i < len; i++) {
         char c = str.charAt(i);
         switch (c) {
            case '\b':
               buf.append("\\b");
               break;
            case '\t':
               buf.append("\\t");
               break;
            case '\n':
               buf.append("\\n");
               break;
            case '\f':
               buf.append("\\f");
               break;
            case '\r':
               buf.append("\\r");
               break;
            case '\"':
               buf.append("\\\"");
               break;
            case '\\':
               buf.append("\\\\");
               break;
            default:
               if (c >= 0x0020 && (useRaw || c <= 0x007f)) // visible character in ascii
               {
                  buf.append(c);
               } else {
                  buf.append("\\u");
                  String hex = Integer.toHexString(c);
                  for (int j = 4 - hex.length(); j > 0; j--) {
                     buf.append('0');
                  }
                  buf.append(hex);
               }
         }
      }
      return buf.toString();
   }

   public static String fromLiteral(String str) {
      StringBuffer buf = new StringBuffer();
      for (int i = 0, len = str.length(); i < len; i++) {
         char c = str.charAt(i);
         switch (c) {
            case '\\':
               if (i == str.length() - 1) {
                  buf.append('\\');
                  break;
               }
               c = str.charAt(++i);
               switch (c) {
                  case 'n':
                     buf.append('\n');
                     break;
                  case 't':
                     buf.append('\t');
                     break;
                  case 'r':
                     buf.append('\r');
                     break;
                  case 'u':
                     int value = 0;
                     for (int j = 0; j < 4; j++) {
                        c = str.charAt(++i);
                        switch (c) {
                           case '0':
                           case '1':
                           case '2':
                           case '3':
                           case '4':
                           case '5':
                           case '6':
                           case '7':
                           case '8':
                           case '9':
                              value = (value << 4) + c - '0';
                              break;
                           case 'a':
                           case 'b':
                           case 'c':
                           case 'd':
                           case 'e':
                           case 'f':
                              value = (value << 4) + 10 + c - 'a';
                              break;
                           case 'A':
                           case 'B':
                           case 'C':
                           case 'D':
                           case 'E':
                           case 'F':
                              value = (value << 4) + 10 + c - 'A';
                              break;
                           default:
                              throw new IllegalArgumentException("Malformed \\uxxxx encoding.");
                        }
                     }
                     buf.append((char) value);
                     break;
                  default:
                     buf.append(c);
                     break;
               }
               break;
            default:
               buf.append(c);
         }
      }
      return buf.toString();
   }

   private StringUtility() {
   }
}
